package com.github.yoojia.flow;

import com.github.yoojia.flow.support.ScriptWatchService;
import com.github.yoojia.flow.util.ShutdownHook;
import com.parkingwang.lang.data.Config;
import com.parkingwang.lang.kit.StringKit;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.script.Invocable;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
import java.io.FileReader;
import java.nio.file.Path;
import java.nio.file.Paths;

/**
 * 脚本实现
 * @author 陈小锅 (yoojiachen@gmail.com)
 * @since 1.0.0
 */
public class ScriptingContext extends InitialContext {

    public static final String CONF_NAME_SCRIPT = "script";

    private static final Logger LOGGER = LoggerFactory.getLogger(ScriptingContext.class);

    private static final ScriptWatchService WATCH_SERVICE = new ScriptWatchService();

    private final ScriptEngine mContextEngine;

    static {
        // 监控JS文件变更服务
        WATCH_SERVICE.start();
        ShutdownHook.add(WATCH_SERVICE::stop);
    }

    public ScriptingContext() {
        mContextEngine = new ScriptEngineManager()
                .getEngineByExtension("js");
    }

    @Override
    public void onInit(Context context, Config configs) throws Exception {
        super.onInit(context, configs);
        final String script = getScriptFileName();
        if (StringKit.isEmpty(script)){
            throw new IllegalArgumentException("Config <configs.script> is required");
        }
        if (WATCH_SERVICE.isScriptExists(script)) {
            throw new IllegalArgumentException("Script is exists, file: " + script);
        }
        // 脚本文件保存在scripts目录中
        final Path dir = WATCH_SERVICE.getScriptsRootDirectory();
        final Path file = Paths.get(dir.toString(), script);
        LOGGER.debug("LOAD javascript file：{}", file);
        loadScript(file);
        WATCH_SERVICE.watchScript(file.getFileName().toString(), () -> {
            LOGGER.debug("RELOAD[Modified] javascript file：{}", file);
            loadScript(file);
        });
    }

    @SuppressWarnings("unchecked")
    protected <T> T invokeFunction(String funcName, Object...vars) throws ScriptException, NoSuchMethodException {
        if (mContextEngine instanceof Invocable) {
            Invocable invocable = (Invocable) mContextEngine;
            return (T) invocable.invokeFunction(funcName, vars);
        }else{
            LOGGER.error("Current JavaScript Engine not support <Invocable>！");
            throw new UnsupportedOperationException("Unsupported Invocable");
        }
    }

    private String getScriptFileName() {
        return getInitConfig().getString(CONF_NAME_SCRIPT);
    }

    private void loadScript(Path jsFilePath) {
        try (final FileReader reader = new FileReader(jsFilePath.toFile())) {
            mContextEngine.eval(reader);
        } catch (Exception e) {
            LOGGER.error("Error when load script", e);
            throw new RuntimeException(e);
        }
    }

}
